﻿using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System;
using TomShane.Neoforce.Controls;

namespace Engine2D
{
    public class Scene
    {
        private Engine2D mEngine;
        private IList<SceneObject> mObjects;
        private IList<Tuple<float, SceneObject>> mSortedObjects;
        private BasicEffect mEffect;

        private Camera mCamera;

        private Interactor mInteractor;

        public GraphicsDevice GraphicsDevice { get { return mEngine.GraphicsDevice; } }
        public ContentManager Content { get { return mEngine.Content; } }
        public Library Library { get { return mEngine.Library; } }
        public Camera Camera { get { return mCamera; } }

        public Scene(Engine2D engine)
        {
            mEngine = engine;
            mObjects = new List<SceneObject>();
            mSortedObjects = new List<Tuple<float, SceneObject>>();
            mEffect = new BasicEffect(GraphicsDevice);

            mCamera = new Camera();
            mCamera.Viewport = new Viewport(new Rectangle(0, 0, mEngine.MainWindow.ClientWidth, mEngine.MainWindow.ClientHeight));

            mInteractor = new Interactor(this);

            GraphicsDevice.DeviceReset += new System.EventHandler<System.EventArgs>(GraphicsDevice_DeviceReset);
        }

        void GraphicsDevice_DeviceReset(object sender, System.EventArgs e)
        {
            mCamera.Viewport = new Viewport(new Rectangle(0, 0, mEngine.MainWindow.ClientWidth, mEngine.MainWindow.ClientHeight));
        }

        public void Update(GameTime gameTime)
        {
            Camera.Update(gameTime);
            mInteractor.Update(gameTime);
        }

        public void Draw(GameTime gameTime)
        {
            SortScene();

            mEffect.View = Camera.View;
            mEffect.Projection = Camera.Projection;
            //mEffect.VertexColorEnabled = true;
            mEffect.TextureEnabled = true;
            
            RasterizerState rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.CullClockwiseFace;
            //rasterizerState.FillMode = FillMode.WireFrame;
            GraphicsDevice.RasterizerState = rasterizerState;

            foreach (var tuple in mSortedObjects)
            {
                SceneObject obj = tuple.Item2;
                mEffect.World = obj.Scale * obj.Rotation * Matrix.CreateTranslation(obj.Position);
                foreach (EffectPass pass in mEffect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    obj.Draw(gameTime);
                }
            }

            mInteractor.Draw(gameTime);
        }

        private void SortScene()
        {
            mSortedObjects.Clear();
            foreach (SceneObject obj in mObjects)
            {
                Vector3 adjustedPos = obj.Position;
                adjustedPos = Vector3.Transform(adjustedPos, Camera.View);
                int index = 0;
                while (index < mSortedObjects.Count && mSortedObjects[index].Item1 < adjustedPos.Z)
                    ++index;
                mSortedObjects.Insert(index, new Tuple<float, SceneObject>(adjustedPos.Z, obj));
            }
        }

        public void AddSceneObject(SceneObject obj)
        {
            obj.Scene = this;
            mObjects.Add(obj);
        }

        public void RemoveSceneObject(SceneObject obj)
        {
            mObjects.Remove(obj);
            obj.Scene = null;
        }

        public SceneObject PickObject(Ray ray, out float t)
        {
            // test all objects and store the hits
            IList<Tuple<SceneObject, float, Vector3>> contacts = new List<Tuple<SceneObject, float, Vector3>>();
            foreach (SceneObject obj in mObjects)
            {
                float pT;
                Vector3 hitPos;
                if (obj.RayHit(ray, out pT, out hitPos))
                {
                    contacts.Add(new Tuple<SceneObject, float, Vector3>(obj, pT, hitPos));
                }
            }

            // go through the contacts and pick the closest
            SceneObject closestObj = null;
            float closestT = float.MaxValue;
            foreach (var vals in contacts)
            {
                if (vals.Item2 < closestT)
                {
                    closestObj = vals.Item1;
                    closestT = vals.Item2;
                }
            }

            t = closestT;
            return closestObj;
        }

        public void OnMouseDown(MouseEventArgs e)
        {
            mInteractor.OnMouseDown(e);
        }

        public void OnMouseMove(MouseEventArgs e)
        {
            mInteractor.OnMouseMove(e);
        }

        public void OnMouseUp(MouseEventArgs e)
        {
            mInteractor.OnMouseUp(e);
        }

        public void OnMouseScroll(MouseEventArgs e)
        {
            mInteractor.OnMouseScroll(e);
        }

        public void OnKeyDown(KeyEventArgs e)
        {
            //mInteractor.OnKeyDown(e);
        }

        public void OnKeyUp(KeyEventArgs e)
        {
            //mInteractor.OnKeyUp(e);
        }
    }
}
